package org.tgcloud.group.core.WXUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.qiniu.http.Response;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;
import org.tgcloud.group.core.*;
import org.tgcloud.group.core.EncryptUtil.MD5;
import org.tgcloud.group.core.WXUtils.alipay.AlipayCore;
import org.tgcloud.group.core.WXUtils.wxpay.SmallZhiRequest;
import org.tgcloud.group.core.WXUtils.wxpay.TelRequest;
import org.tgcloud.group.core.WXUtils.wxpay.WXPayUtil;
import org.tgcloud.group.web.exception.CommonException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.imageio.ImageIO;
import javax.net.ssl.SSLContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

/**
 * Created by Administrator on 2015/12/16 0016.
 */
@Component
public class WeiConfig extends BaseLogger {

    @Autowired
    private HttpClientUtil clientUtil;


    public static String appId = "wx08c3969d54867d5a"; // 小程序id

    public static String app_secret = "9492019d293c151c2cb551bc3493d9b1"; // 小程序秘钥

    public static String mchId = "1605102840"; // 商户号

    public static String notify_url = "https://app.netbaobaobao.com/baozi_up/app/wx_notify_url";

    public static String client_key = "cki0hAZ5jbAu38ph3pWhrFa5zjdgJUcl"; // 商户支付秘钥

    public static String smallAppAccessToken;

    public static String serverAccessToken;

    /**
     * 密钥算法
     */
    private static final String ALGORITHM = "AES";
    /**
     * 加解密算法/工作模式/填充方式
     */
    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";

    @Value("api_file_path")
    private static String file_path;

    public void getAccessToken() {
        String appId = WeiConfig.appId;
        String appSecret = WeiConfig.app_secret;
        String msg = clientUtil.doGet("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret, null);
        Map info = JSON.parseObject(msg, new TypeReference<Map>() {
        });
        logger.error("获取token:" + msg);
        WeiConfig.smallAppAccessToken = info.get("access_token").toString();
    }


    /**
     * @return
     * @Author SGY
     * @Date 2019/7/1 13:36
     * @Param
     * @Description // 小程序微信授权登录,获取openid
     **/
    public Map<String, String> wxLogin(String code) {
        String appId = WeiConfig.appId;
        String appSecret = WeiConfig.app_secret;
        String msg = clientUtil.doGet("https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code", (Map) null);
        Map<String, String> info = JSON.parseObject(msg, new TypeReference<Map>() {
        });
        return info;
    }

    /**
     * @return
     * @Author SGY
     * @Date 2019/10/25 10:57
     * @Param
     * @Description // 生成支付二维码
     **/
    public String generateCode(String orderId, String remoteHost, BigDecimal amount) {
        String res = "";
        try {
            Map<String, String> paras = new HashMap<>();
            paras.put("appid", appId);
            paras.put("mch_id", mchId);
            paras.put("nonce_str", StringUtil.getRandomString());
            paras.put("body", "购买商品");

            paras.put("out_trade_no", orderId);
            paras.put("total_fee", amount.multiply(new BigDecimal(100)).longValue() + "");
            paras.put("spbill_create_ip", remoteHost);
            paras.put("notify_url", notify_url);
            paras.put("trade_type", "NATIVE");
            paras.put("attach", "order"); // order-订单支付 charge-充值支付

            String sign = WXPayUtil.generateSignature(paras, client_key); // MD5方式
            paras.put("sign", sign);

            String xml = WXPayUtil.mapToXml(paras);
            String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

            String result = clientUtil.doPostXML(url, xml);

            Map<String, String> map = WXPayUtil.xmlToMap(result);

            logger.error(orderId + "_" + JSONObject.toJSONString(map));
            //微信预支付单成功创建
            if (map.get("return_code").equals("SUCCESS")
                    && map.get("result_code").equals("SUCCESS")) {
                //使用二维码生成工具，把微信返回的codeUrl转为二维码图片，保存到磁盘
                res = map.get("code_url");

//                File file = new File("C:\\Users\\SGY\\Desktop",orderId+".jpg");
//                createImage(codeUrl, new FileOutputStream(file));
//                createImage(codeUrl, response.getOutputStream());
            }
        } catch (Exception e) {
            printErrorInfo(e);
            e.printStackTrace();
        }
        return res;
    }


    public static void main(String[] args) {
        WeiConfig weiConfig = new WeiConfig();
    }



    /**
     * @return
     * @Author SGY
     * @Date 2019/7/1 13:36
     * @Param
     * @Description // code换access_token, 拿到openid
     **/
    public Map<String, String> getAccessToken(String code) {
        String msg = clientUtil.doGet("https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + app_secret + "&code=" + code + "&grant_type=authorization_code", (Map) null);
        Map<String, String> info = JSON.parseObject(msg, new TypeReference<Map>() {
        });
        String errcode = info.get("errcode");
        if ("40029".equals(errcode)) {
            return null;
        }
        return info;
    }


    /**
     * @return
     * @Author SGY
     * @Date 2019/7/1 13:36
     * @Param
     * @Description // 获取个人信息及unionid
     **/
    public Map<String, String> getUserInfoAndUnionid(String accessToken, String oppenid) {
        String msg = clientUtil.doGet("https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + oppenid, (Map) null);
        Map<String, String> info = JSON.parseObject(msg, new TypeReference<Map>() {
        });
        return info;
    }

    /**
     * @param orderCode  订单号
     * @param refundCode 退款单号
     * @param money      退款金额
     * @param total      退款总金额
     * @return
     * @Author SGY
     * @Date 2020/4/7 16:40
     * @Description // 退款
     **/
    public int wxRefund(String orderCode, String refundCode, BigDecimal money, BigDecimal total, String type) {
        Map<String, String> res = null;
        int r = 0;

        try {
            Map<String, String> paras = new HashMap<>();
            paras.put("appid", appId);
            paras.put("mch_id", mchId);
            paras.put("nonce_str", type);

            paras.put("out_trade_no", orderCode);
            paras.put("total_fee", total.multiply(new BigDecimal(100)).longValue() + "");
            paras.put("out_refund_no", refundCode);
//            paras.put("sub_mch_id", null);
            paras.put("refund_fee", money.multiply(new BigDecimal(100)).longValue() + ""); // 可选，自定义参数，存放第三方地址
//            paras.put("notify_url", ""); // 通知回调地址

            String sign = WXPayUtil.generateSignature(paras, client_key); // MD5方式
            paras.put("sign", sign);

            String xml = WXPayUtil.mapToXml(paras);
            String url = "https://api.mch.weixin.qq.com/secapi/pay/refund";

//            String result = clientUtil.doPostXML(url, xml);
            String result = refundHttpRequest(mchId, url, xml, file_path);

            Map<String, String> map = WXPayUtil.xmlToMap(result);

            logger.error("微信退款请求" + JSONObject.toJSONString(map));

            if (map.get("return_code").equals("SUCCESS")
                    && map.get("result_code").equals("SUCCESS")) {
                r = 1000;
                return r;
            } else {
                String err_code = map.get("err_code");
                if (StringUtil.isBlank(err_code)) {
                    return r;
                } else if ("TRADE_OVERDUE".equals(err_code)) { // 订单已经超过可退款的最大期限(支付后一年内可退款)
                    return 1004;
                } else if ("NOTENOUGH".equals(err_code)) { // 商户可用退款余额不足
                    return 1001;
                } else {
                    return 1003;
                }
            }
        } catch (Exception e) {
            logger.info(e.getMessage());
            e.printStackTrace();
        }
        return r;
    }

    /**
     * 向微信退款发送请求
     *
     * @param mchid 商户号
     * @param url   微信退款请求路径
     * @param parm  请求的参数
     * @return
     */

    public static String refundHttpRequest(String mchid, String url, String parm, String filepath) {
        /**
         * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
         */
        KeyStore keyStore;
        try {
            keyStore = KeyStore.getInstance("PKCS12");
            File file = (ResourceUtils.getFile(filepath));//证书路径
            FileInputStream instream = new FileInputStream(file);
            keyStore.load(instream, mchid.toCharArray());//这里写密码..默认是你的MCHID
            SSLContext sslcontext = SSLContexts.custom()
                    .loadKeyMaterial(keyStore, mchid.toCharArray())//这里密码
                    .build();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            CloseableHttpClient httpclient = HttpClients.custom()
                    .setSSLSocketFactory(sslsf)
                    .build();
            try {
                HttpPost httpost = new HttpPost(url);
                httpost.setEntity(new StringEntity(parm, "UTF-8"));
                CloseableHttpResponse response = httpclient.execute(httpost);
                try {
                    HttpEntity entity = response.getEntity();
                    String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                    EntityUtils.consume(entity);
                    return jsonStr;
                } finally {
                    response.close();
                }
            } finally {
                httpclient.close();
            }

        } catch (KeyStoreException e) {
            printErrorInfo(e);
        } catch (NoSuchAlgorithmException e) {
            printErrorInfo(e);
        } catch (CertificateException e) {
            printErrorInfo(e);
        } catch (IOException e) {
            printErrorInfo(e);
        } catch (KeyManagementException e) {
            printErrorInfo(e);
        } catch (UnrecoverableKeyException e) {
            printErrorInfo(e);
        }
        return null;
    }

    /**
     * @param
     * @return
     * @Author SGY
     * @Date 2020/5/7 18:29
     * @Description // 退款解密
     **/
    public String decryptRefundData(String base64Data) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
        SecretKeySpec key = new SecretKeySpec(MD5.MD5Encode(mchId).toLowerCase().getBytes(), ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] decode = decode(base64Data);
        byte[] doFinal = cipher.doFinal(decode);
        return new String(doFinal, "utf-8");
    }

    /**
     * 解码
     *
     * @param encodedText
     * @return
     */
    public static byte[] decode(String encodedText) {
        final Base64.Decoder decoder = Base64.getDecoder();
        return decoder.decode(encodedText);
    }

    /**
     * 编码
     *
     * @param data
     * @return
     */
    public static String encode(byte[] data) {
        final Base64.Encoder encoder = Base64.getEncoder();
        return encoder.encodeToString(data);
    }


    /**
     * @return
     * @Author zhy
     * @Date 2019/11/3 13:36
     * @Param
     * @Description // 获取个人信息
     **/
    public Map<String, String> getUserInfoAndOpenId(String accessToken, String openid) {
        String msg = clientUtil.doGet("https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN", null);
        Map<String, String> info = JSON.parseObject(msg, new TypeReference<Map>() {
        });
        return info;
    }


    public Map<String, String> getUserInfoByOpenId(String openid) {
        if (smallAppAccessToken == null) {
            getAccessToken();
        }
        String msg = clientUtil.doGet("https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + smallAppAccessToken + "&openid=" + openid + "&lang=zh_CN", null);
        Map info = JSON.parseObject(msg, new TypeReference<Map>() {
        });
        Integer errcode = (Integer) info.get("errcode");
        if (errcode.intValue() == 42001) {
            smallAppAccessToken = null;
            getUserInfoByOpenId(openid);
        } else if (errcode.intValue() == 40001) {
            getAccessToken();
            getUserInfoByOpenId(openid);
        }
        return info;
    }



    /**
     * 生成小程序二维码
     *
     * @param path
     * @param filePath
     */
    public void genrateSmallCode(String path, String filePath) throws IOException {
        if (smallAppAccessToken == null) {
            getAccessToken();
        }
        Map param = Maps.newHashMap();
        param.put("path", path);
        HttpPost post = new HttpPost("https://api.weixin.qq.com/wxa/getwxacode?access_token=" + WeiConfig.smallAppAccessToken);
        StringEntity entity = null;
        FileOutputStream outImgStream = null;

        String json = JSON.toJSONString(param);
        entity = new StringEntity(json, "utf-8");
        post.addHeader("Content-Type", "text/xml");
        post.setEntity(entity);
        HttpResponse response = clientUtil.client.execute(post);
        if (response.getStatusLine().getStatusCode() == 200) {
            Header contentType = response.getHeaders("Content-Type")[0];
            if (contentType.getValue().contains("json")) {
                InputStream is = new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity()));

                byte[] by = new byte[1024];
                int tem;
                StringBuffer buffer = new StringBuffer();
                while ((tem = is.read(by)) != -1) {
                    buffer.append(new String(by, 0, by.length));
                }
                logger.error("小程序二维码生成失败-{}", buffer.toString());
                is.close();
                Map info = JSON.parseObject(buffer.toString().trim(), new TypeReference<Map>() {
                });
                Integer errcode = (Integer) info.get("errcode");
                if (errcode.intValue() == 42001) {
                    smallAppAccessToken = null;
                    genrateSmallCode(path, filePath);
                } else if (errcode.intValue() == 40001) {
                    getAccessToken();
                    genrateSmallCode(path, filePath);
                } else {
                    throw new CommonException("地推二维码生成失败");
                }
            } else {
                // Getting image data from a InputStream
                InputStream is = new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity()));

                int index;
                byte[] bytes = new byte[1024];
                outImgStream = new FileOutputStream(filePath);
                while ((index = is.read(bytes)) != -1) {
                    outImgStream.write(bytes, 0, index);
                    outImgStream.flush();
                }
                outImgStream.close();
                is.close();
            }
        }
    }

    /**
     * 小程序支付
     * @param orderCode
     * @param remoteHost
     * @param amount
     * @return
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    public APPResultMessage unifiedorderSmall(String orderCode, String remoteHost, BigDecimal amount, String openid, String notifyUrl, String attach) throws ParserConfigurationException, IOException, SAXException {
        BigDecimal totalM = amount;
        TelRequest request = new TelRequest("购买产品",
                orderCode, totalM.multiply(new BigDecimal(100)).setScale(2,
                BigDecimal.ROUND_HALF_UP).intValue(), remoteHost,notifyUrl,attach,null);
        request.setOpenid(openid);
        HttpPost post = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
        StringEntity entity = null;
        try {
            entity = new StringEntity(request.getTelResponse(), "utf-8");
            post.addHeader("Content-Type", "text/xml");
            post.setEntity(entity);
            HttpResponse response = clientUtil.client.execute(post);
            if (response.getStatusLine().getStatusCode() == 200) {
                Map<String, String> params = new HashMap<>();
                String result = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8"));
//                Document document = DocumentHelper.parseText(result);
//                Element nodesElement = document.getRootElement();
//                List nodes = nodesElement.elements();
//                for (Iterator its = nodes.iterator(); its.hasNext(); ) {
//                    Element nodeElement = (Element) its.next();
//                    params.put(nodeElement.getName(), nodeElement.getStringValue());
//                }

                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                String FEATURE = null;
                // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
                // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl
                FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
                dbf.setFeature(FEATURE, true);

                // If you can't completely disable DTDs, then at least do the following:
                // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
                // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
                // JDK7+ - http://xml.org/sax/features/external-general-entities
                FEATURE = "http://xml.org/sax/features/external-general-entities";
                dbf.setFeature(FEATURE, false);

                // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
                // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
                // JDK7+ - http://xml.org/sax/features/external-parameter-entities
                FEATURE = "http://xml.org/sax/features/external-parameter-entities";
                dbf.setFeature(FEATURE, false);

                // Disable external DTDs as well
                FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
                dbf.setFeature(FEATURE, false);

                // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
                dbf.setXIncludeAware(false);
                dbf.setExpandEntityReferences(false);

                // And, per Timothy Morgan: "If for some reason support for inline DOCTYPEs are a requirement, then
                // ensure the entity settings are disabled (as shown above) and beware that SSRF attacks
                // (http://cwe.mitre.org/data/definitions/918.html) and denial
                // of service attacks (such as billion laughs or decompression bombs via "jar:") are a risk."

                // remaining parser logic
                DocumentBuilder safebuilder = dbf.newDocumentBuilder();
                org.w3c.dom.Document doc = safebuilder.parse(new ByteArrayInputStream(result.getBytes("UTF-8")));
                doc.getDocumentElement().normalize();
                NodeList nodes = doc.getDocumentElement().getChildNodes();
                for (int i = 0; i < nodes.getLength(); i++) {
                    Node nodeElement = nodes.item(i);
                    params.put(nodeElement.getNodeName(), nodeElement.getTextContent());
                }
                String sign = params.remove("sign");
                Map<String, String> sPara = AlipayCore.paraFilter(params);
                sPara.remove("#text");
                //生成签名结果
                String mySign = StringUtil.MD5Encode(AlipayCore.createLinkStringNoYinHao(sPara) + "&key=" + WeiConfig.client_key).toUpperCase();
                logger.error("微信支付-{}", params);
                if (mySign.equals(sign)) {
                    if ("SUCCESS".equals(params.get("return_code")) && "SUCCESS".equals(params.get("result_code"))) {
                        APPResultMessage out = new APPResultMessage(1000, "微信支付信息生成完毕");
                        Map<String, String> telResponse = new SmallZhiRequest(params.get("prepay_id")).getTelResponse();
                        telResponse.put("orderId",orderCode);
//                        CommonConfigInfo commonConfigInfo = commonConfigInfoMapper.selectOne();
//                        ShopDiscount discount = null;
//                        if (commonConfigInfo.getPayDisId() != null){
//                            discount = discountMapper.selectByPrimaryKey(commonConfigInfo.getPayDisId());
//                        }
//                        telResponse.put("disMsg",JSON.toJSONString(discount));
                        out.setResult(ResultMessage.builder().setData(telResponse));
                        return out;
                    } else {
                        logger.error("微信支付订单申请失败");
                        return APPResultMessage.getErrorMessage("微信支付订单申请失败");
                    }
                } else {
                    logger.error("签名不匹配");
                    return APPResultMessage.getErrorMessage("签名不匹配");
                }
            } else {
                logger.error("状态不为200");
                return APPResultMessage.getErrorMessage("微信出错，请重试");
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("微信出错，请重试");
            return APPResultMessage.getErrorMessage("微信出错，请重试");
        }

    }

    //发送订阅消息
    public Map<String, String> sendSmallAppSubscribeMessage(String openId, Map data, String path, String tempId) {
        if (smallAppAccessToken == null) {
            getAccessToken();
        }
        Map param = Maps.newHashMap();
        param.put("touser", openId);
        if (!Strings.isNullOrEmpty(path)) {
            param.put("page", path);
        }
        param.put("template_id", tempId);

        param.put("data", data);

        HttpPost post = new HttpPost("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + WeiConfig.smallAppAccessToken);
        StringEntity entity = null;
        try {
            String json = JSON.toJSONString(param);
            logger.info("请求数据：" + json);
            entity = new StringEntity(json, "utf-8");
            post.addHeader("Content-Type", "text/xml");
            post.setEntity(entity);
            HttpResponse response = clientUtil.client.execute(post);
            if (response.getStatusLine().getStatusCode() == 200) {
                String msg = EntityUtils.toString(response.getEntity());
                logger.info("发送消息:" + msg);
                Map info = JSON.parseObject(msg, new TypeReference<Map>() {
                });
                if ((Integer) info.get("errcode") == 40001) {
                    getAccessToken();
                    info = sendSmallAppSubscribeMessage(openId, data, path, tempId);
                } else if ((Integer) info.get("errcode") == 42001) {
                    getAccessToken();
                    info = sendSmallAppSubscribeMessage(openId, data, path, tempId);
                }
                return info;
            }
        } catch (ClientProtocolException e) {
            printErrorInfo(e);
        } catch (IOException e) {
            printErrorInfo(e);
        }
        return null;
    }


}
